/* beeper.c
* device driver for beeper of mini2440
*/
/

#include <linux/config.h>			/* kernel configuration */
#include <linux/module.h>			/* modules */
#include <linux/kernel.h>			/* printk */
#include <linux/fs.h>				/* file-system */
#include <linux/init.h>		
#include <linux/devfs_fs_kernel.h>	/* dev file-system */
#include <asm-arm/types.h>			/* types */
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <plat/regs-timer.h>		/* timer register */

#define BEEPER_NAME					"beeper"
#define BEEPER_MAJOR				242

/* pin definition */
#define BEEPER_PIN					S3C2410_GPB0
#define BEEPER_PIN_OUTP				S3C2410_GPB0_OUTP
#define BEEPER_PIN_INP				S3C2410_GPB0_INP

#define BEEPER_IOCTL_SET_FREQ		1
#define BEEPER_IOCTL_STOP			2

/* beeper could be openned only by one process! */
static struct semaphore lock;

/*
* marked by liuk 20090710.
* Set the working frequency of PWM.
* More detail of PWM timer should refer to hardware handbook!
*/
static int beeper_set_freq(unsigned long freq)
{
	unsigned long tcon;
	unsigned long tcnt;
	unsigned long tcfg1;
	unsigned long tcfg0;

	struct clk *clk_p;
	unsigned long pclk;

	//set GPB0 as tout0, pwm output
	s3c2410_gpio_cfgpin(BEEPER_PIN, BEEPER_PIN_OUTP);

	tcon = __raw_readl(S3C2410_TCON);
	tcfg1 = __raw_readl(S3C2410_TCFG1);
	tcfg0 = __raw_readl(S3C2410_TCFG0);

	//prescaler = 50
	tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
	tcfg0 |= (50 - 1); 

	//mux = 1/16
	tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK;
	tcfg1 |= S3C2410_TCFG1_MUX0_DIV16;

	__raw_writel(tcfg1, S3C2410_TCFG1);
	__raw_writel(tcfg0, S3C2410_TCFG0);

	clk_p = clk_get(NULL, "pclk");
	pclk  = clk_get_rate(clk_p);
	tcnt  = (pclk/50/16)/freq;
	
	__raw_writel(tcnt, S3C2410_TCNTB(0));
	__raw_writel(tcnt/2, S3C2410_TCMPB(0));
				
	tcon &= ~0x1f;
	tcon |= 0xb;		//disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
	__raw_writel(tcon, S3C2410_TCON);
	
	tcon &= ~2;			//clear manual update bit
	__raw_writel(tcon, S3C2410_TCON);
}

static void beeper_stop(void)
{
	s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);
	s3c2410_gpio_setpin(S3C2410_GPB0, 0);
}

static int s3c2440_beeper_open(struct inode *inode, struct file *file)
{
	if (!down_trylock(&lock))
		return 0;
	else
		return -EBUSY;
}


static int s3c2440_beeper_close(struct inode *inode, struct file *file)
{
	up(&lock);
    return 0;
}


static int s3c2440_beeper_ioctl(struct inode *, struct file *, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
		case BEEPER_IOCTL_SET_FREQ:
			if (arg == 0)
				return -EINVAL;
			beeper_set_freq(arg);
			break;

		case BEEPER_IOCTL_STOP:
			beeper_stop();
			break;
	}

	return 0;
}

static struct file_operations s3c2440_beeper_fops =
{

	.owner = THIS_MODULE,
	.open = s3c2440_beeper_open,
	.release = s3c2440_beeper_close,
	.ioctl = s3c2440_beeper_ioctl,
};

/*
* marked by liuk 20090710.
* Beeper is a character device in our project, however in kernel-2.6.29, it
* has been initialized as a miscdevice (see mini2440 kernel-2.6.29).
* I don't know the difference between the two yet.
*/
static int __init s3c2440_beeper_init(void)
{
	if (register_chrdev(BEEPER_MAJOR, BEEPER_NAME, &s3c2440_beeper_fops) < 0) {
		printk(BEEPER_NAME ": Register major failed.\r\n");
		return -1;
	}
	devfs_mk_cdev(MKDEV(BEEPER_MAJOR, 0),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEV_NAME);
	return 0;
}

static void __exit s3c2440_beeper_exit(void)
{
	devfs_remove(BEEPER_NAME);
	unregister_chrdev(BEEPER_MAJOR, BEEPER_NAME);
}

module_init(s3c2440_18b20_init);
module_exit(s3c2440_18b20_exit);

